package mirrormonkey.framework;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import mirrormonkey.framework.annotations.IdentityAware;
import mirrormonkey.framework.entity.EntityData;
import mirrormonkey.framework.entity.StaticEntityData;
import mirrormonkey.framework.entity.StaticEntityDataIR;
import mirrormonkey.framework.entity.SyncEntity;
import mirrormonkey.framework.parameter.IdentityAwareInterpreter;
import mirrormonkey.framework.parameter.NullInterpreter;
import mirrormonkey.framework.parameter.ParameterInterpreter;
import mirrormonkey.rpc.RpcSpecification;
import mirrormonkey.util.annotations.parsing.AnnotationParser;

public class EntityProvider {

	public static final Integer NULL_ID = -1;

	protected final Map<ClassDataKey, StaticEntityData> staticData;

	protected final Collection<StaticEntityData> updatableStaticData;

	protected final Map<Integer, EntityData> dynamicData;

	public static final Collection<Class<?>> INTERFACE_BOUND = new ArrayList<Class<?>>() {
		{
			add(RpcSpecification.class);
		}
	};

	protected final AnnotationParser parser;

	protected final ParameterInterpreter nullInterpreter;

	protected final ParameterInterpreter identityAwareInterpreter;

	protected SyncAppState appState;

	public EntityProvider() {
		this(new AnnotationParser());
	}

	public EntityProvider(AnnotationParser parser) {
		staticData = new HashMap<ClassDataKey, StaticEntityData>();
		updatableStaticData = new LinkedList<StaticEntityData>();
		dynamicData = new HashMap<Integer, EntityData>();
		this.parser = parser;
		nullInterpreter = new NullInterpreter();
		identityAwareInterpreter = new IdentityAwareInterpreter(this);
	}

	public StaticEntityData getStaticData(
			Class<? extends SyncEntity> localClass,
			Class<? extends SyncEntity> connectedClass) {
		return getStaticData(localClass, connectedClass, true);
	}

	public StaticEntityData getStaticData(
			Class<? extends SyncEntity> localClass,
			Class<? extends SyncEntity> connectedClass, boolean add) {
		return getStaticData(new ClassDataKey(localClass, connectedClass), add,
				appState.isCollectLocalConstructors());
	}

	public StaticEntityData getStaticData(ClassDataKey key, boolean add,
			boolean collectLocalConstructors) {
		StaticEntityData data = staticData.get(key);
		if (data == null) {
			StaticEntityDataIR ir = (StaticEntityDataIR) parser.parseClass(
					key.localClass, INTERFACE_BOUND);
			data = ir.extractData(key.localClass, key.connectedClass, appState,
					collectLocalConstructors);
			if (add) {
				addStaticData(key, data);
			}
		}
		return data;
	}

	public void addStaticData(ClassDataKey key, StaticEntityData data) {
		staticData.put(key, data);
		if (data.requiresUpdate()) {
			updatableStaticData.add(data);
		}
	}

	public void parsePresetClass(Class<?> clazz) {
		parser.parsePresetClass(clazz);
	}

	protected void setAppState(SyncAppState appState) {
		this.appState = appState;
	}

	protected static final class ClassDataKey {

		protected final Class<? extends SyncEntity> localClass;

		protected final Class<? extends SyncEntity> connectedClass;

		public ClassDataKey(Class<? extends SyncEntity> localClass,
				Class<? extends SyncEntity> connectedClass) {
			this.localClass = localClass;
			this.connectedClass = connectedClass;
		}

		@Override
		public final int hashCode() {
			return 13 * localClass.hashCode() + connectedClass.hashCode();
		}

		@Override
		public final boolean equals(Object o) {
			if (!o.getClass().equals(ClassDataKey.class)) {
				return false;
			}
			ClassDataKey cdk = (ClassDataKey) o;
			return cdk.localClass.equals(localClass)
					&& cdk.connectedClass.equals(connectedClass);
		}

		@Override
		public String toString() {
			return "ClassDataKey[" + localClass.getName() + " : "
					+ connectedClass.getName() + "]";
		}

	}

	@Override
	public String toString() {
		return dynamicData.toString();
	}

	protected void update(float tpf) {
		for (StaticEntityData i : updatableStaticData) {
			i.update(tpf);
		}
	}

	public void registerData(EntityData data) {
		System.out.println("REGISTER " + data);
		System.out.println("REGISTER " + data.getId());
		dynamicData.put(Integer.valueOf(data.getId()), data);
	}

	public EntityData getData(int entityId) {
		return dynamicData.get(Integer.valueOf(entityId));
	}

	public void removeData(EntityData data) {
		removeData(data.getId());
	}

	public void removeData(int entityId) {
		dynamicData.remove(Integer.valueOf(entityId));
	}

	public ParameterInterpreter getInterpreter(IdentityAware annotation) {
		return annotation == null ? nullInterpreter : identityAwareInterpreter;
	}

	public ParameterInterpreter[] getInterpreters(Map[] paramAnnotations) {
		ParameterInterpreter[] interpreters = new ParameterInterpreter[paramAnnotations.length];
		int i = 0;
		for (Map m : paramAnnotations) {
			interpreters[i] = getInterpreter((IdentityAware) m
					.get(IdentityAware.class));
			i++;
		}
		return interpreters;
	}

	public void assertNotOutdated(EntityData entityData) {
		if (!entityData.isRegistered()) {
			if (entityData.isEntityPresent()) {
				if (entityData.getEntity().getData() != null) {
					throw new IllegalStateException();
				}
			} else if (dynamicData.get(entityData.getId()) != null) {
				throw new IllegalStateException();
			}
		} else {
			if (entityData.isEntityPresent()) {
				if (entityData.getEntity().getData() != entityData) {
					throw new IllegalStateException();
				}
			} else if (dynamicData.get(entityData.getId()) != entityData) {
				throw new IllegalArgumentException();
			}
		}
	}

	public boolean hasEntities() {
		return !dynamicData.isEmpty();
	}

	public Collection<EntityData> getEntities() {
		return dynamicData.values();
	}
}
